"""Excel session storage abstractions."""
from __future__ import annotations

import threading
import time
from abc import ABC, abstractmethod
from collections import OrderedDict
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple

import pandas as pd


@dataclass
class ExcelSessionRecord:
    session_id: str
    file_id: str
    file_name: str
    temp_path: str
    dataframe: pd.DataFrame
    created_at: float


class ExcelSessionStore(ABC):
    """Abstract store for keeping uploaded excel DataFrames."""

    @abstractmethod
    def put(self, record: ExcelSessionRecord) -> List[ExcelSessionRecord]:
        """Persist a record. Returns records that were evicted."""

    @abstractmethod
    def get(self, session_id: str, file_id: str) -> Optional[ExcelSessionRecord]:
        """Fetch a record."""

    @abstractmethod
    def clear_session(self, session_id: str) -> List[ExcelSessionRecord]:
        """Remove all files for a session. Returns deleted records."""

    @abstractmethod
    def evict_expired(self, max_age_seconds: float) -> List[ExcelSessionRecord]:
        """Evict entries older than the provided age."""


class InMemoryExcelSessionStore(ExcelSessionStore):
    """Simple in-memory store with per-session and global limits."""

    def __init__(self, max_sessions: int = 100, max_files_per_session: int = 3) -> None:
        self.max_sessions = max_sessions
        self.max_files_per_session = max_files_per_session
        self._sessions: "OrderedDict[str, OrderedDict[str, ExcelSessionRecord]]" = OrderedDict()
        self._lock = threading.Lock()

    def put(self, record: ExcelSessionRecord) -> List[ExcelSessionRecord]:
        evicted: List[ExcelSessionRecord] = []
        with self._lock:
            session_cache = self._sessions.setdefault(record.session_id, OrderedDict())
            if record.file_id in session_cache:
                old = session_cache.pop(record.file_id)
                evicted.append(old)
            session_cache[record.file_id] = record
            # move session to end for LRU
            self._sessions.move_to_end(record.session_id)

            # per-session limit
            while len(session_cache) > self.max_files_per_session:
                _, removed = session_cache.popitem(last=False)
                evicted.append(removed)

            # global limit
            while len(self._sessions) > self.max_sessions:
                session_id, files = self._sessions.popitem(last=False)
                evicted.extend(files.values())
        return evicted

    def get(self, session_id: str, file_id: str) -> Optional[ExcelSessionRecord]:
        with self._lock:
            session_cache = self._sessions.get(session_id)
            if not session_cache:
                return None
            record = session_cache.get(file_id)
            if not record:
                return None
            # touch session for LRU
            self._sessions.move_to_end(session_id)
            return record

    def clear_session(self, session_id: str) -> List[ExcelSessionRecord]:
        with self._lock:
            session_cache = self._sessions.pop(session_id, None)
            if not session_cache:
                return []
            return list(session_cache.values())

    def evict_expired(self, max_age_seconds: float) -> List[ExcelSessionRecord]:
        expired: List[ExcelSessionRecord] = []
        now = time.time()
        with self._lock:
            sessions_to_delete: List[str] = []
            for session_id, files in self._sessions.items():
                file_ids_to_remove: List[str] = []
                for file_id, record in files.items():
                    if now - record.created_at > max_age_seconds:
                        expired.append(record)
                        file_ids_to_remove.append(file_id)
                for file_id in file_ids_to_remove:
                    files.pop(file_id, None)
                if not files:
                    sessions_to_delete.append(session_id)
            for session_id in sessions_to_delete:
                self._sessions.pop(session_id, None)
        return expired

